package cmd

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"github.com/spf13/cobra"
)

var (
	searchQuery         string
	searchDir           string
	searchLimit         int
	searchRegex         bool
	searchCaseSensitive bool
	searchIgnores       []string
	searchListAllDirs   bool
	searchMatchDir      bool
)

// searchCmd 提供文件的快速模糊查询，输出匹配文件所在的目录
var searchCmd = &cobra.Command{
	Use:   "search",
	Short: "模糊查询文件所在目录",
	Long:  "在指定目录下递归搜索（支持模糊或正则匹配），可按文件名或目录名匹配，输出匹配文件的所在目录列表。",
	RunE:  runSearch,
}

func init() {
	// 注册子命令
	rootCmd.AddCommand(searchCmd)

	// 选项
	searchCmd.Flags().StringVarP(&searchQuery, "query", "q", "", "查询关键字（必填），支持模糊或正则")
	searchCmd.Flags().StringVarP(&searchDir, "dir", "d", ".", "搜索根目录（默认当前目录）")
	searchCmd.Flags().IntVarP(&searchLimit, "limit", "n", 50, "最多返回的目录数量")
	searchCmd.Flags().BoolVar(&searchRegex, "regex", false, "将查询关键字视为正则表达式")
	searchCmd.Flags().BoolVar(&searchCaseSensitive, "case-sensitive", false, "大小写敏感匹配（默认不敏感）")
	searchCmd.Flags().StringSliceVarP(&searchIgnores, "ignore", "i", []string{".git", "node_modules", "vendor", ".idea", ".vscode", "dist", "build"}, "忽略的目录名称（精确匹配）")
	searchCmd.Flags().BoolVar(&searchListAllDirs, "all-dirs", false, "输出所有匹配文件的目录（不去重）")
	searchCmd.Flags().BoolVar(&searchMatchDir, "match-dir", false, "按目录名匹配（默认按文件名匹配）")
}

func runSearch(cmd *cobra.Command, args []string) error {
	if strings.TrimSpace(searchQuery) == "" {
		return errors.New("请通过 --query 指定查询关键字")
	}

	// 规范化根目录
	root := strings.TrimSpace(searchDir)
	if root == "" {
		root = "."
	}
	absRoot, err := filepath.Abs(root)
	if err != nil {
		absRoot = root
	}

	// 构造匹配器
	matcher, mErr := buildMatcher(searchQuery, searchRegex, searchCaseSensitive)
	if mErr != nil {
		return mErr
	}

	// 扫描
	dirs, err := scanForMatchingDirs(absRoot, matcher, searchIgnores, searchLimit, searchListAllDirs, searchMatchDir)
	if err != nil {
		return err
	}

	if len(dirs) == 0 {
		fmt.Println("未找到匹配的文件目录")
		return nil
	}

	// 输出
	for i, d := range dirs {
		if i >= searchLimit {
			break
		}
		fmt.Println(d.Path)
	}
	return nil
}

type nameMatcher func(name string) bool

func buildMatcher(query string, asRegex bool, caseSensitive bool) (nameMatcher, error) {
	if asRegex {
		// 编译正则（根据大小写敏感决定是否加 (?i)）
		rx := query
		if !caseSensitive {
			rx = "(?i)" + query
		}
		re, err := regexp.Compile(rx)
		if err != nil {
			return nil, fmt.Errorf("正则编译失败: %w", err)
		}
		return func(name string) bool {
			return re.MatchString(name)
		}, nil
	}
	// 模糊（子串）匹配
	if !caseSensitive {
		query = strings.ToLower(query)
	}
	return func(name string) bool {
		if !caseSensitive {
			name = strings.ToLower(name)
		}
		return strings.Contains(name, query)
	}, nil
}

type dirHit struct {
	Path  string
	Count int
}

func scanForMatchingDirs(root string, match nameMatcher, ignores []string, limit int, listAll bool, matchDir bool) ([]dirHit, error) {
	ignoreSet := make(map[string]struct{}, len(ignores))
	for _, ig := range ignores {
		ignoreSet[ig] = struct{}{}
	}

	counts := make(map[string]int)
	hitsAll := make([]dirHit, 0, 128)

	err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return nil
		}
		name := d.Name()

		// 跳过忽略目录
		if d.IsDir() {
			if _, skip := ignoreSet[name]; skip {
				return filepath.SkipDir
			}
			// 跳过符号链接目录（避免循环）
			if d.Type()&os.ModeSymlink != 0 {
				return filepath.SkipDir
			}
			// 匹配目录名
			if matchDir && match(name) {
				if listAll {
					hitsAll = append(hitsAll, dirHit{Path: path, Count: 1})
				} else {
					counts[path]++
				}
			}
			return nil
		}

		// 非目录：文件名匹配
		if !matchDir && match(name) {
			dir := filepath.Dir(path)
			if listAll {
				hitsAll = append(hitsAll, dirHit{Path: dir, Count: 1})
			} else {
				counts[dir]++
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	// 组装输出
	if listAll {
		// 排序：按路径字典序，保留重复
		sort.Slice(hitsAll, func(i, j int) bool { return hitsAll[i].Path < hitsAll[j].Path })
		if limit > 0 && len(hitsAll) > limit {
			hitsAll = hitsAll[:limit]
		}
		return hitsAll, nil
	}

	// 去重统计：按命中次数降序，其次目录路径字典序
	hits := make([]dirHit, 0, len(counts))
	for p, c := range counts {
		hits = append(hits, dirHit{Path: p, Count: c})
	}
	sort.Slice(hits, func(i, j int) bool {
		if hits[i].Count != hits[j].Count {
			return hits[i].Count > hits[j].Count
		}
		return hits[i].Path < hits[j].Path
	})

	if limit > 0 && len(hits) > limit {
		hits = hits[:limit]
	}
	return hits, nil
}
